#ifndef DODELEGATE_H
#define DODELEGATE_H

/*
Copyright (c) 2013, ebaklund
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, 
   this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, 
   this list of conditions and the following disclaimer in the documentation 
   and/or other materials provided with the distribution.

3. Neither the name of the <ORGANIZATION> nor the names of its contributors 
   may be used to endorse or promote products derived from this software 
   without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#include <Inventor/DoSyntacticSugar.h>
#include <Inventor/DoTypeTraits.h>
#include <Do/CountedVisitor.h>
       
#include <assert.h>
#include <memory>
#include <functional>
#include <unordered_set>
#include <tuple>
#include <utility>


namespace Do
{ 
  template <typename R, typename... Args> 
  class CallbackBaseMixin
  {
  protected:          
    mutable int circular_invoke_counter_;

  public:
    CallbackBaseMixin() : circular_invoke_counter_(0) 
    {
    }

    virtual void invoke(Args const&... args) const = 0;    
    virtual bool expired() const = 0;
  };


  template <typename R, typename... Args> 
  class CallbackTryMixin : public CallbackBaseMixin<R, Args...>
  {
  public:
    virtual R tryInvoke(R const& fallback, Args const&... args) const = 0;
  };
  

  template <typename R, typename... Args> 
  struct CallbackSuperClassResolver
  {
    typedef typename std::conditional< 
      std::is_void<R>::value, 
      CallbackBaseMixin<R, Args...>, 
      CallbackTryMixin<R, Args...>
    >::type SuperClass;
  };


  template <typename R, typename... Args> 
  class Callback : public CallbackSuperClassResolver<R, Args...>::SuperClass
  {
  public:
    typedef Callback<R, Args...> ThisClass;

    virtual u_ptr<ThisClass> clone() const = 0;
    virtual size_t hash() const = 0;
    virtual bool equals(ThisClass const& that) const = 0;
  };


  template <typename R, typename... Args>
  class FunctionCallbackBaseMixin : public Callback<R, Args...>
  {
  private:
    FunctionCallbackBaseMixin(); // Ban

  protected:
    R (*func_ptr_)(Args...);
    size_t hash_;

  public:
    typedef FunctionCallbackBaseMixin<R, Args...> ThisClass;

    FunctionCallbackBaseMixin(R (*func_ptr)(Args...)) 
      : func_ptr_(func_ptr),
        hash_(std::hash<R(*)(Args...)>()(func_ptr))
    {}

    FunctionCallbackBaseMixin(ThisClass const& that) 
      : FunctionCallbackBaseMixin(that.func_ptr_)
    {}

    virtual size_t hash() const 
    {
      return hash_;
    }

    virtual bool equals(Callback<R, Args...> const& other) const 
    {
      ThisClass const* that = dynamic_cast<ThisClass const*>(&other);

      if (that == nullptr)
        return false;

      if (this->func_ptr_ != that->func_ptr_)
        return false;

      return true;
    }

    virtual void invoke(Args const&... args) const
    {
      CountedVisitor counted_visitor(circular_invoke_counter_);
      assert(counted_visitor.isAlone());

      if (counted_visitor.isAlone())
        (*func_ptr_)(args...);
    }

    virtual bool expired() const
    { 
      // Static function pointers cannot expire
      return false; 
    }

  };


  template <typename R, typename... Args>
  class FunctionCallbackTryMixin : public FunctionCallbackBaseMixin<R, Args...>
  {
  private:
    typedef FunctionCallbackBaseMixin<R, Args...> SuperClass;

  public:  
    FunctionCallbackTryMixin(R (*func_ptr)(Args...)) 
      : SuperClass(func_ptr)
    {}

    FunctionCallbackTryMixin(FunctionCallbackTryMixin<R, Args...> const& other) 
      : SuperClass(other)
    {}

    virtual R tryInvoke(R const& fallbackValue, Args const&... args) const {
      CountedVisitor counted_visitor(circular_invoke_counter_);
      assert(counted_visitor.isAlone());

      if (!counted_visitor.isAlone())
        return fallbackValue;
      else
      return (*func_ptr_)(args...);
    }
  };


  template <typename R, typename... Args> 
  struct FunctionCallbackSuperClassResolver
  {
    typedef typename std::conditional< 
      std::is_void<R>::value, 
      FunctionCallbackBaseMixin<R, Args...>, 
      FunctionCallbackTryMixin<R, Args...>
    >::type SuperClass;
  };


  template <typename R, typename... Args> 
  class FunctionCallback : public FunctionCallbackSuperClassResolver<R, Args...>::SuperClass
  {
  private:
    typedef typename FunctionCallbackSuperClassResolver<R, Args...>::SuperClass SuperClass;

  public:  
    FunctionCallback(R (*func_ptr)(Args...)) 
      : SuperClass(func_ptr)
    {}

    FunctionCallback(FunctionCallback<R, Args...> const& other) 
      : SuperClass(other)
    {}

    virtual u_ptr<Callback<R, Args...>> clone() const {
      return mk_u<FunctionCallback<R, Args...>>(func_ptr_);
    }
  };


  template <typename R, typename T, typename... Args>
  class MethodCallbackBaseMixin : public Callback<R, Args...>
  {
  protected:
    void*     raw_obj_ptr_;       // Object pointer. Supports equals(). Regrettfully in want from std::weak_ptr.
    w_ptr<T>  weak_obj_ptr_;      // Object pointer. Tracks destruction of object. 
    R (T::*method_ptr_)(Args...); // Method pointer.
    size_t    hash_;

    size_t getHash(void* raw_obj_ptr, R (T::*method_ptr)(Args...)) {
      std::hash<void*> hasher;
      return hasher(raw_obj_ptr) ^ hasher(*reinterpret_cast<void**>(&method_ptr));
    }
  
  public:
    typedef MethodCallbackBaseMixin<R, T, Args...> ThisClass;

    MethodCallbackBaseMixin(void* raw_obj_ptr, w_ptr<T>&& weak_obj_ptr, R (T::*method_ptr)(Args...), size_t const& hash)
      : raw_obj_ptr_(raw_obj_ptr),
        weak_obj_ptr_(std::forward<w_ptr<T>>(weak_obj_ptr)),
        method_ptr_(method_ptr),
        hash_(hash)
    {}

    virtual size_t hash() const {
      return hash_;
    }

    virtual bool equals(Callback<R, Args...> const& other) const {

      ThisClass const* that = dynamic_cast<ThisClass const*>(&other);

      if (that == nullptr)
        return false;

      if (this->raw_obj_ptr_ != that->raw_obj_ptr_)
        return false;

      if  (this->method_ptr_ != that->method_ptr_)
        return false;

      return true;
    }

    virtual void invoke(Args const&... args) const
    {
      CountedVisitor counted_visitor(circular_invoke_counter_);
      assert(counted_visitor.isAlone());

      if (counted_visitor.isAlone() && !weak_obj_ptr_.expired())
        (weak_obj_ptr_.lock().get()->*method_ptr_)(args...);
    }

    virtual bool expired() const
    {
      return weak_obj_ptr_.expired();
    }
  };


  template <typename R, typename T, typename... Args>
  class MethodCallbackTryMixin : public MethodCallbackBaseMixin<R, T, Args...>
  {
  private:
    typedef MethodCallbackBaseMixin<R, T, Args...> SuperClass;

  public:

    MethodCallbackTryMixin(void* raw_obj_ptr, w_ptr<T>&& weak_obj_ptr, R (T::*method_ptr)(Args...), size_t const& hash)
      : SuperClass(raw_obj_ptr, std::forward<w_ptr<T>>(weak_obj_ptr), method_ptr, hash )
    {}

    virtual R tryInvoke(R const& fallback, Args const&... args) const
    {
      CountedVisitor counted_visitor(circular_invoke_counter_);
      assert(counted_visitor.isAlone());

      if (counted_visitor.isAlone() && !weak_obj_ptr_.expired())
        return (weak_obj_ptr_.lock().get()->*method_ptr_)(args...);
      else
        return fallback;
    }
  };

  
  template <typename R, typename T, typename... Args> 
  struct MethodCallbackSuperClassResolver
  {
    typedef typename std::conditional< 
      std::is_void<R>::value, 
      MethodCallbackBaseMixin<R, T, Args...>, 
      MethodCallbackTryMixin<R, T, Args...>
    >::type SuperClass;
  };


  template <typename R, typename T, typename... Args>
  class MethodCallback : public MethodCallbackSuperClassResolver<R, T, Args...>::SuperClass
  {
  private:
    typedef typename MethodCallbackSuperClassResolver<R, T, Args...>::SuperClass SuperClass;

  public:
    MethodCallback(s_ptr<T>const& shared_obj_ptr, R (T::*method_ptr)(Args...))
      : SuperClass(shared_obj_ptr.get(), shared_obj_ptr, method_ptr, getHash(shared_obj_ptr.get(), method_ptr))
    {}

    MethodCallback(s_ptr<T>&& shared_obj_ptr, R (T::*method_ptr)(Args...))
      : SuperClass(shared_obj_ptr.get(), std::forward<s_ptr<T>>(shared_obj_ptr), method_ptr, getHash(shared_obj_ptr.get(), method_ptr))
    {}

    MethodCallback(void* raw_obj_ptr, w_ptr<T>&& weak_obj_ptr, R (T::*method_ptr)(Args...), size_t const& hash)
      : SuperClass(raw_obj_ptr, std::forward<w_ptr<T>>(weak_obj_ptr), method_ptr, hash )
    {}

    virtual u_ptr<Callback<R, Args...>> clone() const {
      return mk_u<MethodCallback<R, T, Args...>>(raw_obj_ptr_, weak_obj_ptr_, method_ptr_, hash_);
    }
  };


  template <typename R, typename... Args>
  class DelegateBaseMixin
  {
  private:
    typedef Callback<R, Args...> CB;

  protected:
    u_ptr<CB> callback_;
  
  public:
    DelegateBaseMixin(R (*func)(Args...))
    : callback_(mk_u<FunctionCallback<R, Args...>>(func)) {
    }

    template <typename T>
    DelegateBaseMixin(s_ptr<T> const& object, R (T::*method)(Args...))
      : callback_(mk_u<MethodCallback<R, T, Args...>>(object, method)) {
    }

    DelegateBaseMixin(DelegateBaseMixin<R, Args...> const& that)
      : callback_(mk_u<CB>(that)) {
    }

    void invoke(Args const&... args) const {
        callback_->invoke(args...);
    }

    Callback<R, Args...> const& callback() const {
      return *callback_;
    }

  };


  template <typename R, typename... Args>
  class DelegateTryMixin : public DelegateBaseMixin<R, Args...>
  {
  private:
    typedef DelegateBaseMixin<R, Args...> SuperClass;

  public:
    DelegateTryMixin(R (*func)(Args...))
      : SuperClass(func)
    {}

    template <typename T>
    DelegateTryMixin(s_ptr<T> const& object, R (T::*method)(Args...))
      : SuperClass(object, method)
    {}

    DelegateTryMixin(DelegateTryMixin<R, Args...> const& that)
      : SuperClass(that)
    {}

    typename R tryInvoke(R const& fallback, Args const&... args) const
    {
      return callback_->tryInvoke(fallback, args...);
    }
  };

  
  template <typename R, typename... Args> 
  struct DelegateSuperClassResolver
  {
    typedef typename std::conditional< 
      std::is_void<R>::value, 
      DelegateBaseMixin<R, Args...>, 
      DelegateTryMixin<R, Args...>
    >::type SuperClass;
  };


  template <typename R, typename... Args>
  class Delegate : public DelegateSuperClassResolver<R, Args...>::SuperClass
  {
  private:
    typedef typename DelegateSuperClassResolver<R, Args...>::SuperClass SuperClass;

  public:
    Delegate(R (*func)(Args...))
      : SuperClass(func) {
      Do::static_assert_valid_callback_signature<0, Args...>();
    }

    template <typename T>
    Delegate(s_ptr<T> const& object, R (T::*method)(Args...))
      : SuperClass(object, method) {
      Do::static_assert_valid_callback_signature<0, Args...>();
    }

    Delegate(DelegateTryMixin<R, Args...> const& that)
      : SuperClass(that) {
      Do::static_assert_valid_callback_signature<0, Args...>();
    }
  };


  template <typename R, typename... Args>
  class EventBaseMixin
  {
  private:
    typedef Callback<R, Args...> CB;

    struct CallbackHash {
      size_t operator()(u_ptr<CB> const& cb0) {
        return cb0->hash();
      }
    };

    struct CallbackEqual {
      bool operator()(u_ptr<CB> const& cb0, u_ptr<CB> const& cb1) {
        return cb0->equals(*cb1);
      }
    };

    std::unordered_set<u_ptr<CB>, CallbackHash, CallbackEqual> callbacks_;

  public:

    typedef Delegate<R, Args...> D;

    void operator += (D const& delegate) {
      callbacks_.insert(delegate.callback().clone());
    }

    void operator -= (D const& delegate) {
      callbacks_.erase(delegate.callback().clone());
    }

    template <
      typename FCB = FunctionCallback<R, Args...>
    >
    void operator += (R (*func)(Args...)) {
      callbacks_.insert(mk_u<FCB>(func));
    }

    template <
      typename FCB = FunctionCallback<R, Args...>
    >
    void operator -= (R (*func)(Args...)) {
      callbacks_.erase(mk_u<FCB>(func));
    }

    template<
      typename T, 
      typename MCB = MethodCallback<R, T, Args...>, 
      typename TP = std::tuple<T, R (T::*)(Args...)>
    >
    void operator += (TP const& tuple) {
      callbacks_.insert(mk_u<MCB>(tuple[0], tuple[1]));
    }

    template<
      typename T, 
      typename MCB = MethodCallback<R, T, Args...>, 
      typename TP = std::tuple<T, R (T::*)(Args...)>
    >
    void operator -= (TP const& tuple) {
      callbacks_.erase(mk_u<MCB>>(tuple[0], tuple[1]));
    }

    void invoke(Args const&... args)
    {
      for(auto i = callbacks_.begin(); i != callbacks_.end();)
      {
        if ((*i)->expired()) {
          i = callbacks_.erase(i);
        } 
        else {
          (*i)->invoke(args...);
          ++i;
        }
      }
    }

    int count() const {
      return callbacks_.size();
    }
  };


  template <typename R, typename... Args>
  class EventTryMixin : public EventBaseMixin<R, Args...>
  {
  public:
    u_ptr<std::vector<R>> tryMapInvoke(Args const&... args) {
      return mk_u<std::vetor<R>>();
    }

    R tryFoldInvoke(R const& fallback, Args const&... args) {
      return fallback;
    }
  };

    
  template <typename R, typename... Args>
  struct EventSuperClassResolver
  {
    typedef typename std::conditional< 
      std::is_void<R>::value, 
      EventBaseMixin<R, Args...>, 
      EventTryMixin<R, Args...>
    >::type SuperClass;
  };


  
  template <typename R, typename... Args>
  class Event : public EventSuperClassResolver<R, Args...>::SuperClass
  {
  public:
    Event() {
     Do::static_assert_valid_callback_signature<0, Args...>();
    }
  };

} // Do namespace

#endif // DODELEGATE_H
